#
# Copyright (C) 2011: Gabriel Tremblay (initnull@gmail.com)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
import urllib

__author__ = 'Gabriel Tremblay <initnull@gmail.com>'
import wx
from TouTvUtils import TouTvUtils
import vlc
import sys
from InterceptorProxy import InterceptorProxy
import urllib

# Todo: Extract logic code for feed retreival
# Todo: Refactor UI building to separate component creation in sub methods
# Todo: Fix duplicate sound output glitch under windows.
class MainFrame(wx.Frame):
    # Tree ctrl
    tree = wx.TreeCtrl

    # Output play panel
    infoPanel = wx.Panel

    # proxy used to mangle vlc messy http headers
    interceptorProxy = None

    # Custom Headers for the proxy interceptor
    customHeaders = {'User-Agent' : 'AppleCoreMedia/1.0.0.8F5166b (iPad; U; CPU OS 4_3 like Mac OS X; en_us)',
                    'Accept' : '*/*',
                    'Accept-Encoding' : 'identity',
                    'Connection' : 'keep-alive'}

    # Constructor
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(700, 400))
        self.Bind(wx.EVT_CLOSE, self.onClose)

        # Start the interceptor proxy on 127.0.0.1, port 8910 using ipv4
        self.interceptorProxy = InterceptorProxy()
        
        self.interceptorProxy.startProxy('127.0.0.1', 8910, False, injectHeaders=self.customHeaders)

        # Build the layout
        self.buildLayout()

    def onClose(self, event):
        # Stop the proxy
        if self.interceptorProxy:
            self.interceptorProxy.stopProxy()

        # Destroy the app
        self.Destroy()

    # Construct application layout
    def buildLayout(self):
        # Menu Bar
        menubar = wx.MenuBar()

        # Menus
        fileMenu = wx.Menu()
        audioMenu = wx.Menu()
        videoMenu = wx.Menu()
        agentMenu = wx.Menu()
        aboutMenu = wx.Menu()

        # SubMenus
        fileMenu.Append(101, '&Reload Shows', 'Reload the list of shows')
        fileMenu.AppendSeparator()
        fileMenu.Append(102, '&Quit', 'Exit the application')

        # Fetch audio devices
        audioOutputsStartIndex = 200
        audioDevicesStartIndex = 200
        audioOutputs = vlc.Instance().audio_output_enumerate_devices()
        for output in audioOutputs:
            devicesList = output.get('devices')
            if len(devicesList) > 0:
                # Create a submenu to hold the devices
                submenu = wx.Menu()
                description = output.get('description')
                audioMenu.AppendMenu(audioOutputsStartIndex, description, submenu)
                audioOutputsStartIndex += 1

                # Add each devices to a submenu
                for device in devicesList:
                    name = device.get('longname')
                    submenu.Append(audioDevicesStartIndex, name, name, wx.ITEM_RADIO)
                    audioDevicesStartIndex +=1


        # Fetch screen output
        screenStartIndex = 400
        numDisplays = wx.Display.GetCount()
        for displayId in range(numDisplays):
            currentDisplay = wx.Display(displayId)
            displayString = "Screen " + str(displayId) + ": " + str(currentDisplay.Geometry.GetSize())

            if currentDisplay.IsPrimary():
                displayString += " [Primary]"

            videoMenu.Append(screenStartIndex, displayString, "Display", wx.ITEM_RADIO)
            screenStartIndex += 1

        ##To get a wx.Rect that gives the geometry of a display
        #geometry = display.GetGeometry()
        #Create a frame on the display
        #frame = wx.Frame(None,-1,"Display %d"%display_num,
        #geometry.GetTopLeft(),geometry.GetSize())
        #Make the frame visible
        #frame.Show()

        # Fetch userAgents
        agentMenu.Append(103, '&iPad', 'Use iPad User Agent', wx.ITEM_RADIO)
        agentMenu.Append(104, '&iPhone', 'Use iPhone User Agent', wx.ITEM_RADIO)
        agentMenu.Check(103, True)

        aboutMenu.Append(110, '&wxTouTv', "wxTouTv")

        menubar.Append(fileMenu, "&File")
        menubar.Append(audioMenu, "&Audio output")
        menubar.Append(videoMenu, "&Fullscreen output")
        menubar.Append(agentMenu, "&User Agent")
        menubar.Append(aboutMenu, "&About")

        # Set menubar
        self.SetMenuBar(menubar)

        # status bar
        self.CreateStatusBar()

        # Main splitter
        splitter = wx.SplitterWindow(self, -1)

        # Container panel, TreeControl and Display info panel
        treePanel = wx.Panel(splitter, -1)
        self.infoPanel = wx.Panel(splitter, -1)

        # Tree with minimum 25px size
        self.tree = wx.TreeCtrl(treePanel, 1, wx.DefaultPosition,(25, 25), wx.TR_HAS_BUTTONS | wx.SUNKEN_BORDER)

        # Vertical sizer for tree control
        verticalTreeSizer = wx.BoxSizer(wx.VERTICAL)
        verticalTreeSizer.Add(self.tree, 1, wx.EXPAND)
        treePanel.SetSizer(verticalTreeSizer)
        splitter.SplitVertically(treePanel, self.infoPanel, 275)
        splitter.SetMinimumPaneSize(20);

        # Show form
        self.Centre()
        self.Show(True)

        # Input handling
        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelChanged, id=1)
        self.tree.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnTreeSelChanged, id=1)

        # Load shows directory
        touTvUtils = TouTvUtils()
        self.directory = touTvUtils.fetchDirectory(self.loadDirectoryCallback)


    # Tree selection changed or expand button clicked
    def OnTreeSelChanged(self, event):
        # Get item and PyData
        selectedItem = event.GetItem()
        itemData = self.tree.GetPyData(selectedItem)

        if itemData:
            # identify selected item
            # {'showId': 1394101520, 'episodesLoaded': False, 'type': 1}
            if itemData.get('type') is 1 and itemData.get('episodesLoaded') is False:
                # Expand item if not already expanded
                if not self.tree.IsExpanded(selectedItem):
                    self.tree.Expand(selectedItem)

                    # Start a new fetching thread
                    self.SetStatusText("Fetching episodes")
                    touTvUtils = TouTvUtils()
                    touTvUtils.fetchEpisodeDetails(itemData.get('showId'),
                                                   self.loadEpisodesCallback, False, selectedItem)
            elif itemData.get('type') is 2:
                # Play
                episodeDetail = itemData.get('episodeData')
                pid = episodeDetail.get('PID')
                if pid:
                    touTvUtils = TouTvUtils()
                    touTvUtils.getStreamUrl(pid, self.getEpisodeStreamCallback, False, selectedItem)
                else:
                    self.SetStatusText("Failed to get episode PID")

    # Received episode stream, play it on screen
    def getEpisodeStreamCallback(self, caller):
        dict = caller.directory
        url = dict.get('url')
        self.playEpisode(self.infoPanel, url)


    # Called when the show directory is loaded
    def loadDirectoryCallback(self, caller):
        root = self.tree.AddRoot("Shows")
        tmpDic = caller.directory

        # Show loading failed
        if not tmpDic:
            self.SetStatusText("loading Shows Directory Failed.")
        else:
            shows = tmpDic.get("Emissions")

            # Create a new directory with the episode title as the key and episode values as values.
            directory = dict((x.get("Titre"), x) for x in shows)

            # Create an alphabetically sorted Title list for display purposes
            sortedDirectory = list(x for x in directory.keys())
            sortedDirectory.sort()

            # Populate the first root children level with show entries
            # PyData: dict() {
            # type: 1
            # showId: id
            # episodesLoaded = False }
            for show in sortedDirectory:
                showInfos = {
                    'type' : 1,
                    'showId' : directory.get(show).get('Id'),
                    'episodesLoaded' : False,
                    'showData' : directory.get(show)
                }
                subItem = self.tree.AppendItem(root, show)
                self.tree.SetPyData(subItem, showInfos)
                self.tree.AppendItem(subItem, "Loading episodes...")

            self.tree.Expand(root)


    # Called when the episode list is done loading
    def loadEpisodesCallback(self, caller):
        # Fetch item from caller
        item = caller.callback_args[0]

        if not item:
            # Failed to locate which item owns this episodes list
            self.SetStatusText("Unable to find show for retreived episodes")
        else:
            # Sorted list for display purposes
            tmpDic = caller.directory

            # Episode loading Test
            if not tmpDic:
                self.SetStatusText("Episode loading failed")
            else:
                # Clean "loading episode"
                self.tree.DeleteChildren(item)

                episodes = tmpDic.get("Episodes")
                episodesDict = dict((x.get("SeasonAndEpisode") + " - " + x.get("Title"), x) for x in episodes)

                #pprint.pprint(episodesDict)
                # Create a sorted list
                sortedEpisodes = list(x for x in episodesDict.keys())
                sortedEpisodes.sort()

                # Type 2, shows
                for episode in sortedEpisodes:
                    showInfos = {
                        'type': 2,
                        'episodeId': episodesDict.get(episode).get('PID'),
                        'episodeData' : episodesDict.get(episode)
                    }
                    subItem = self.tree.AppendItem(item, episode)
                    self.tree.SetPyData(subItem, showInfos)


    # Play an episode
    def playEpisode(self, panel, episodeurl):
        # Create vlc instance, an embeddable media player and a media list player to parse the .m3u8
        instance = vlc.Instance('--http-forward-cookies', '--http-proxy=127.0.0.1:8910', '--prefer-system-codecs')
        listPlayer = vlc.MediaListPlayer()
        player = instance.media_player_new()

        # Set the panel handle to the embeddable player
        panelHandle = panel.GetHandle()
        if sys.platform == "win32":
            player.set_hwnd(panelHandle)
        elif sys.platform == "linux2":
            player.set_xwindow(panelHandle)
        else:
            player.set_nsobject(panelHandle)

        # Replace media List Player player with the embedded one
        listPlayer.set_media_player(player)

        # Map the remote playlist to a media
        # Hack some chars
        print episodeurl
        media = instance.media_new(episodeurl)

        # Add the media to a playlist (thus parsing it)
        mediaList = instance.media_list_new()
        mediaList.add_media(media)

        # Set the player playlist
        listPlayer.set_media_list(mediaList)

        # Play the stream
        listPlayer.play()

